#include <aura.h>
#include "private.h"
#include <sys/resource.h>
#include <sys/types.h>
#include <sched.h>
#include <time.h>
#include <unistd.h>
#include <linux/unistd.h>
#include <sys/mman.h>
#include <auraplus.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>

EXTERN_C int SendCreateThreadDbgInfo(int fd, int tid);
extern int             g_iDbgFlag_fd;

extern Aura_UInt32 g_ThreadDescrSlot;

typedef struct {
    PAuraThreadMain m_pUserEntry;
    Aura_PVoid m_pUserArgs;
    Aura_PVoid m_pThreadDescr;
    Aura_Bool  m_bSuspend;
}ThreadParams;

static
void * AuraThreadEntry(void * pArgs)
{
    ThreadParams * pParams = (ThreadParams *)pArgs;
    PAuraThreadMain pEntry = pParams->m_pUserEntry;
    Aura_PVoid    pUsrArgs = pParams->m_pUserArgs;
    ThreadDescr  * pDescr  = NULL;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL != pParams);

    pDescr = (ThreadDescr *)(pParams->m_pThreadDescr);
    assert(NULL != pDescr);

    assert((Aura_UInt32)(-1) != g_ThreadDescrSlot);
    ec = AuraTls_SetValue(g_ThreadDescrSlot, pDescr);
    assert(AURA_NOERROR == ec);

    if (pParams->m_bSuspend) {
        Aura_WaitResult wr;
        Aura_EventState state;

#if _DEBUG
        ec =
#endif
        AuraEvent_Wait(pDescr->m_hStartEvent,
                        -1,
                        &wr,
                        &state);
#if _DEBUG
        assert(AURA_NOERROR == ec);
#endif
    }
    Aura_free(pArgs);

    if(g_iDbgFlag_fd){
        SendCreateThreadDbgInfo(g_iDbgFlag_fd, pDescr->m_threadId);
    }

    AuraThread_Quit(pEntry(pUsrArgs));

    pthread_exit(0);
    assert(0);
    return NULL;
}

AURA_API AuraThread_Create(
        /* [in] */ PAuraThreadMain pEntry,
        /* [in] */ Aura_PVoid pArguments,
        /* [in] */ Aura_ThreadCreationFlags creationFlags,
        /* [out] */ Aura_Handle * phThread,
        /* [out] */ Aura_ThreadId * pThreadId)
{
    ThreadParams * pParams = NULL;
    ThreadDescr  * pDescr  = NULL;
    Aura_ECode ec = AURA_NOERROR;
    pthread_attr_t thread_attr;
    Aura_Int32 ret = 0;
    pthread_t tid = 0;
    //int stackBase = 0;
    //int stackSize = 0;

#define STACK_SIZE       (2*1024*1024)

    pParams = (ThreadParams *)Aura_malloc(sizeof(ThreadParams));
    if (NULL == pParams) {
        ec = AURA_E_OUT_OF_MEMORY;
        goto E_FAIL_EXIT;
    }
    pParams->m_pUserEntry = pEntry;
    pParams->m_pUserArgs = pArguments;
    pParams->m_bSuspend = AURA_FALSE;

    if (creationFlags & Aura_ThreadCreationFlag_Unstarted) {
        pParams->m_bSuspend = AURA_TRUE;
    }

    pDescr = (ThreadDescr *)Aura_malloc(sizeof(ThreadDescr));
    if (NULL == pDescr) {
        ec = AURA_E_OUT_OF_MEMORY;
        goto E_FAIL_EXIT;
    }

    memset((void *)pDescr, 0, sizeof(ThreadDescr));
    pDescr->m_bMainThread = AURA_FALSE;

    if (pParams->m_bSuspend) {
        ec = AuraEvent_Create(AURA_FALSE,
                             AURA_UNSIGNALED,
                             &pDescr->m_hStartEvent);
        if (AURA_FAILED(ec)) {
            ec = GetLastAuraECode();
            goto E_FAIL_EXIT;
        }
    }

    ec = AuraEvent_Create(AURA_FALSE,
                         AURA_UNSIGNALED,
                         &pDescr->m_hQuitEvent);
    if (AURA_FAILED(ec)) {
        ec = GetLastAuraECode();
        goto E_FAIL_EXIT;
    }

    pParams->m_pThreadDescr = (Aura_PVoid)pDescr;

    pthread_attr_init(&thread_attr);
    pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
    pthread_attr_setstacksize(&thread_attr, STACK_SIZE);

    ret = pthread_create(&tid, &thread_attr, AuraThreadEntry, (void *)pParams);
    if (0 != ret) {
        ec = GetLastAuraECode();
        goto E_FAIL_EXIT;
    }

    pDescr->m_threadId = tid;

   // pthread_getattr_np(tid, &thread_attr);
   // pthread_attr_getstack(&thread_attr, (void **)&stackBase, (size_t *)&stackSize);
   // pDescr->m_desktop = stackBase + stackSize; //Bug Bug Bug, the value of desktop is not true

    *pThreadId = tid;
    *phThread = (Aura_Handle)pDescr;

    return AURA_NOERROR;

E_FAIL_EXIT:
    if (NULL != pParams) {
        Aura_free(pParams);
    }
    if (NULL != pDescr) {
        if (pDescr->m_hStartEvent) {
            AuraEvent_Destroy(pDescr->m_hStartEvent);
        }
        if (pDescr->m_hQuitEvent) {
            AuraEvent_Destroy(pDescr->m_hQuitEvent);
        }
        Aura_free(pDescr);
    }
    return ec;
}

AURA_API AuraThread_Resume(
        /* [in] */ Aura_Handle hThread)
{
    ThreadDescr * pDescr = (ThreadDescr *)hThread;
    assert(NULL != pDescr);

    return AuraEvent_Signal(pDescr->m_hStartEvent, AURA_SIGNALED);
}

AURA_API AuraThread_Suspend(
        /* [in] */ Aura_Handle hThread)
{
    FixMe("Not Implemented.");
    return AURA_E_NOT_IMPLEMENTED;
}

AURA_API AuraThread_Join(
        /* [in] */ Aura_Handle hThread,
        /* [in] */ Aura_Millisecond32 timeOut,
        /* [out] */ Aura_WaitResult * pResult)
{
    ThreadDescr * pDescr = (ThreadDescr *)hThread;
    Aura_EventState state = 0;

    assert(NULL != pDescr);
    assert(NULL != pResult);
    if (NULL == pDescr || NULL == pResult) {
        return AURA_E_INVALID_OPERATION;
    }

    return AuraEvent_Wait(pDescr->m_hQuitEvent, timeOut, pResult, &state);
}

AURA_API AuraThread_Abort(
        /* [in] */ Aura_Handle hThread,
        /* [in] */ Aura_QuitCode quitCode)
{
    ThreadDescr * pDescr = (ThreadDescr *)hThread;
    int ret = 0;

    assert(NULL != hThread);

    // In Android, we can only use pthread_kill
    ret = pthread_cancel(pDescr->m_threadId);
    //ret = pthread_kill(pDescr->m_threadId, SIG_ABORTTHREAD);
    if (ret != 0) {
        goto E_FAIL_EXIT;
    }
    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastAuraECode();
}

AURA_API AuraThread_Interrupt(
        /* [in] */ Aura_Handle hThread)
{
    FixMe("Not Implemented.");
    return AURA_E_NOT_IMPLEMENTED;
}

AURA_API AuraThread_Destroy(
        /* [in] */ Aura_Handle hThread)
{
    ThreadDescr * pDescr = (ThreadDescr *)hThread;

    assert(NULL != pDescr);

    if (NULL != pDescr->m_hStartEvent) {
        AuraEvent_Destroy(pDescr->m_hStartEvent);
    }
    if (NULL != pDescr->m_hQuitEvent) {
        AuraEvent_Destroy(pDescr->m_hQuitEvent);
    }

    if (!pDescr->m_bMainThread) {
        Aura_free(pDescr);
    }

    return AURA_NOERROR;
}

AURA_API AuraThread_GetPriority(
        /* [in] */ Aura_Handle hThread,
        /* [out] */ Aura_ThreadPriority * pPriority)
{
    ThreadDescr * pDescr = (ThreadDescr *)hThread;
    int lxpriority = 0;

    errno = 0;
    lxpriority = getpriority(PRIO_PROCESS, pDescr->m_threadId);
    if(-1 == lxpriority && 0 != errno) {
        goto E_FAIL_EXIT;
    }

    *pPriority = ConvertToAuraPriority(lxpriority);

    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastAuraECode();
}

AURA_API AuraThread_SetPriority(
        /* [in] */ Aura_Handle hThread,
        /* [in] */ Aura_ThreadPriority priority)
{

    int lxpriority = 0, res = 0;
    ThreadDescr * pDescr = (ThreadDescr *)hThread;

    lxpriority = ConvertToLinuxPriority(priority);
    res = setpriority(PRIO_PROCESS, pDescr->m_threadId, lxpriority);
    if(-1 == res) {
        goto E_FAIL_EXIT;
    }

    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastAuraECode();
}

AURA_API AuraThread_GetProcessorTimes(
        /* [in] */ Aura_Handle hThread,
        /* [out] */  Aura_Millisecond32 * pUserTime,
        /* [out] */  Aura_Millisecond32 * pPrivilegedTime)
{
    struct rusage usage;
    int res = 0;

    assert((NULL != pUserTime)
         ||(NULL != pPrivilegedTime));

    res = getrusage(RUSAGE_SELF, &usage);
    if(res < 0){
        goto E_FAIL_EXIT;
    }

    if (NULL != pUserTime) {
        *pUserTime = usage.ru_utime.tv_sec * 1000 \
                   + usage.ru_utime.tv_usec / 1000;
    }
    if (NULL != pPrivilegedTime) {
        *pPrivilegedTime = usage.ru_stime.tv_sec * 1000 \
                         + usage.ru_stime.tv_usec / 1000;
    }
    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastAuraECode();
}

static Aura_ECode GetMaiThreadStackAddr(
        /* [out] */ Aura_Address *pStackTop)
{
    char filename[32];
    char content[1024];
    char stack_bottom[32];
    char *pContent = NULL, *pTmp = NULL;
    unsigned long stackBottom = 0;
    int fd = 0;
    int res;

    sprintf(filename, "/proc/%d/stat", getpid());
    fd = open(filename, O_RDONLY);
    if (fd < 0) {
        goto E_FAIL_EXIT;
    }

    res = read(fd, content, 1024);
    if (res < 0) {
        goto E_FAIL_EXIT;
    }
    close(fd);

    pContent = content;
    for (int i = 1; i < 28; i++) {
        pContent = strstr(pContent, " ");
        pContent = pContent + 1;
    }

    pTmp = strstr(pContent, " ");
    strncpy(stack_bottom, pContent, pTmp-pContent);
    stack_bottom[pTmp-pContent] = '\0';
    sscanf(stack_bottom, "%lu", &stackBottom);

    *pStackTop = (Aura_Address)stackBottom;
    return AURA_NOERROR;

E_FAIL_EXIT:
    if(fd > 0) {
        close(fd);
    }
    return GetLastAuraECode();
 
}

AURA_API AuraThread_GetUserDesktop(
        /* [in] */ Aura_Handle hThread,
        /* [out] */ Aura_Address * pDesktop)
{
    ThreadDescr * pDescr = (ThreadDescr *)hThread;

    assert(NULL != hThread);
    assert(NULL != pDesktop);

    if (pDescr->m_bMainThread) {
        return GetMaiThreadStackAddr(pDesktop);
    }

    FixMe("Not Implemented.");
    return AURA_E_NOT_IMPLEMENTED;
}

AURA_API_(Aura_Void) AuraThread_Sleep(
        /* [in] */ Aura_Millisecond32 timeOut,
        /* [out] */ Aura_WaitResult * pResult)
{
    struct timespec ts;
    Aura_ECode ec = AURA_NOERROR;
    int mseconds = 0;
    int res = 0;

    assert(NULL != pResult);

    mseconds = InfiniteFilter(timeOut);
    ts.tv_sec  = (time_t)mseconds / 1000;
    ts.tv_nsec = (time_t)(mseconds % 1000)*1000*1000;

    res = nanosleep(&ts, &ts);
    if (res < 0) {
        ec = GetLastAuraECode();
        if (AURA_E_INTERRUPTED != ec) {
            goto E_FAIL_EXIT;
        }
        *pResult = Aura_WaitResult_Interrupted;
    }
    else {
        *pResult = Aura_WaitResult_TimedOut;
    }
    return;

E_FAIL_EXIT:
    return;
}

AURA_API_(Aura_Void) AuraThread_Yield()
{
    sched_yield();
}

AURA_API_(Aura_Void) AuraThread_Quit(
        /* [in] */ Aura_QuitCode quitCode)
{
    ThreadDescr * pDescr = NULL;

    AuraTls_GetValue(g_ThreadDescrSlot, (Aura_PVoid *)&pDescr);
    AuraEvent_Signal(pDescr->m_hQuitEvent, AURA_SIGNALED);

    pthread_exit((void *)quitCode);
    assert(0);
}

AURA_API_(Aura_Handle) AuraThread_GetCurrent()
{
    ThreadDescr * pDescr = NULL;

    AuraTls_GetValue(g_ThreadDescrSlot, (Aura_PVoid *)&pDescr);
    return (Aura_Handle)pDescr;
}

AURA_API_(Aura_ThreadId) AuraThread_GetCurrentId()
{
    ThreadDescr * pDescr = NULL;

    AuraTls_GetValue(g_ThreadDescrSlot, (Aura_PVoid *)&pDescr);
    return pDescr->m_threadId;
}
